// $Id$
#include <stdio.h>
#include <string.h>

////////////////////////////////////////////////////////////////////////////////
// Macro definition
////////////////////////////////////////////////////////////////////////////////
#define BUFFER_SIZE_MESSAGE	2048
#define BUFFER_SIZE_PRIORITY	16
#define BUFFER_SIZE_VERSION	16
#define BUFFER_SIZE_TIMESTAMP	64
#define BUFFER_SIZE_HOSTNAME	64
#define BUFFER_SIZE_APPNAME	64
#define BUFFER_SIZE_PROCID	16
#define BUFFER_SIZE_MSGID	16
#define BUFFER_SIZE_SEGMENT	1024

////////////////////////////////////////////////////////////////////////////////
// Local variables
////////////////////////////////////////////////////////////////////////////////
static char const rcsid[] = "$Id$";

////////////////////////////////////////////////////////////////////////////////
// Function implement
////////////////////////////////////////////////////////////////////////////////
void parse_rfc5424_raw(const char *buf, int len)
{
  int offset;
  int i;

  offset = 0;

  //-----------------------------------------------------------------------------
  // XXX: Calculate priority
  //-----------------------------------------------------------------------------
  // The PRI part MUST have three, four, or five characters and will be
  // bound with angle brackets as the first and last characters.  The PRI
  // part starts with a leading "<" ('less-than' character, %d60),
  // followed by a number, which is followed by a ">" ('greater-than'
  // character, %d62).
  //-----------------------------------------------------------------------------
  char priority[BUFFER_SIZE_PRIORITY];

  memset(priority, 0, sizeof(priority));

  for (offset = 0; buf[offset] != '<'; offset++)
  {
  }

  offset++;

  for (i = 0; buf[offset] != '>'; i++)
  {
    priority[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Version
  //
  // The VERSION field denotes the version of the syslog protocol
  // specification.  The version number MUST be incremented for any new
  // syslog protocol specification that changes any part of the HEADER
  // format.  Changes include the addition or removal of fields, or a
  // change of syntax or semantics of existing fields.  This document uses
  // a VERSION value of "1".  The VERSION values are IANA-assigned
  // (Section 9.1) via the Standards Action method as described in
  // [RFC5226].
  //-----------------------------------------------------------------------------
  char version[BUFFER_SIZE_VERSION];

  memset(version, 0, sizeof(version));

  for (i = 0; buf[offset] != ' '; i++)
  {
    version[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Timestamp
  //
  // The TIMESTAMP field is a formalized timestamp derived from [RFC3339].
  //-----------------------------------------------------------------------------
  char timestamp[BUFFER_SIZE_TIMESTAMP];

  memset(timestamp, 0, sizeof(timestamp));

  for (i = 0; buf[offset] != ' '; i++)
  {
    timestamp[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Hostname
  //
  // The HOSTNAME field identifies the machine that originally sent the
  // syslog message.
  //-----------------------------------------------------------------------------
  char hostname[BUFFER_SIZE_HOSTNAME];

  memset(hostname, 0, sizeof(hostname));

  for (i = 0; buf[offset] != ' '; i++)
  {
    hostname[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Application name
  //
  // The APP-NAME field SHOULD identify the device or application that
  // originated the message.  It is a string without further semantics.
  // It is intended for filtering messages on a relay or collector.
  //
  // The NILVALUE MAY be used when the syslog application has no idea of
  // its APP-NAME or cannot provide that information.  It may be that a
  // device is unable to provide that information either because of a
  // local policy decision, or because the information is not available,
  // or not applicable, on the device.
  //
  // This field MAY be operator-assigned.
  //-----------------------------------------------------------------------------
  char appname[BUFFER_SIZE_APPNAME];

  memset(appname, 0, sizeof(appname));

  for (i = 0; buf[offset] != ' '; i++)
  {
    appname[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Process ID
  //
  // PROCID is a value that is included in the message, having no
  // interoperable meaning, except that a change in the value indicates
  // there has been a discontinuity in syslog reporting.  The field does
  // not have any specific syntax or semantics; the value is
  // implementation-dependent and/or operator-assigned.  The NILVALUE MAY
  // be used when no value is provided.
  //
  // The PROCID field is often used to provide the process name or process
  // ID associated with a syslog system.  The NILVALUE might be used when
  // a process ID is not available.  On an embedded system without any
  // operating system process ID, PROCID might be a reboot ID.
  //
  // PROCID can enable log analyzers to detect discontinuities in syslog
  // reporting by detecting a change in the syslog process ID.  However,
  // PROCID is not a reliable identification of a restarted process since
  // the restarted syslog process might be assigned the same process ID as
  // the previous syslog process.
  //
  // PROCID can also be used to identify which messages belong to a group
  // of messages.  For example, an SMTP mail transfer agent might put its
  // SMTP transaction ID into PROCID, which would allow the collector or
  // relay to group messages based on the SMTP transaction.
  //
  //-----------------------------------------------------------------------------
  char procid[BUFFER_SIZE_PROCID];

  memset(procid, 0, sizeof(procid));

  for (i = 0; buf[offset] != ' '; i++)
  {
    procid[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // XXX: Message ID
  //
  // The MSGID SHOULD identify the type of message.  For example, a
  // firewall might use the MSGID "TCPIN" for incoming TCP traffic and the
  // MSGID "TCPOUT" for outgoing TCP traffic.  Messages with the same
  // MSGID should reflect events of the same semantics.  The MSGID itself
  // is a string without further semantics.  It is intended for filtering
  // messages on a relay or collector.
  //
  // The NILVALUE SHOULD be used when the syslog application does not, or
  // cannot, provide any value.
  //
  // This field MAY be operator-assigned.
  //-----------------------------------------------------------------------------
  char msgid[BUFFER_SIZE_MSGID];

  memset(msgid, 0, sizeof(msgid));

  for (i = 0; buf[offset] != ' '; i++)
  {
    msgid[i] = buf[offset];
    offset++;
  }

  offset++;

  //-----------------------------------------------------------------------------
  // STRUCTURED-DATA
  //
  // STRUCTURED-DATA provides a mechanism to express information in a well
  // defined, easily parseable and interpretable data format.  There are
  // multiple usage scenarios.  For example, it may express meta-
  // information about the syslog message or application-specific
  // information such as traffic counters or IP addresses.
  //
  // STRUCTURED-DATA can contain zero, one, or multiple structured data
  // elements, which are referred to as "SD-ELEMENT" in this document.
  //
  // In case of zero structured data elements, the STRUCTURED-DATA field
  // MUST contain the NILVALUE.
  //
  // The character set used in STRUCTURED-DATA MUST be seven-bit ASCII in
  // an eight-bit field as described in [RFC5234].  These are the ASCII
  // codes as defined in "USA Standard Code for Information Interchange"
  // [ANSI.X3-4.1968].  An exception is the PARAM-VALUE field (see
  // Section 6.3.3), in which UTF-8 encoding MUST be used.
  //
  // A collector MAY ignore malformed STRUCTURED-DATA elements.  A relay
  // MUST forward malformed STRUCTURED-DATA without any alteration.
  //-----------------------------------------------------------------------------
  char segment[BUFFER_SIZE_SEGMENT];

  if (buf[offset] == '-')
  {
    fprintf(stdout, "No structured data.\n");
    offset += 2;
  }
  else
  {
    for (;;)
    {
      offset++;
      memset(segment, 0, sizeof(segment));

      for (i = 0; buf[offset] != ']'; i++)
      {
	segment[i] = buf[offset];
	offset++;
      }

      fprintf(stdout, "Segment: %s\n", segment);

      offset++;

      if (buf[offset] != '[')
	break;
    }
  }

  //-----------------------------------------------------------------------------
  // XXX: Dump result
  //
  // The MSG part contains a free-form message that provides information
  // about the event.
  //
  // The character set used in MSG SHOULD be UNICODE, encoded using UTF-8
  // as specified in [RFC3629].  If the syslog application cannot encode
  // the MSG in Unicode, it MAY use any other encoding.
  //
  // The syslog application SHOULD avoid octet values below 32 (the
  // traditional US-ASCII control character range except DEL).  These
  // values are legal, but a syslog application MAY modify these
  // characters upon reception.  For example, it might change them into an
  // escape sequence (e.g., value 0 may be changed to "\0").  A syslog
  // application SHOULD NOT modify any other octet values.
  //
  // If a syslog application encodes MSG in UTF-8, the string MUST start
  // with the Unicode byte order mask (BOM), which for UTF-8 is ABNF
  // %xEF.BB.BF.  The syslog application MUST encode in the "shortest
  // form" and MAY use any valid UTF-8 sequence.
  //
  // If a syslog application is processing an MSG starting with a BOM and
  // the MSG contains UTF-8 that is not shortest form, the MSG MUST NOT be
  // interpreted as being encoded in UTF-8, for the reasons outlined in
  // [UNICODE-TR36], Section 3.1.  Guidance about this is given in
  // Appendix A.8.
  //
  // Also, according to UNICODE TR36 [UNICODE-TR36], a syslog application
  // MUST NOT interpret messages in the "non-shortest form".  It MUST NOT
  // interpret invalid UTF-8 sequences.
  //-----------------------------------------------------------------------------
  char message[BUFFER_SIZE_MESSAGE];

  memset(message, 0, sizeof(message));
  memcpy(message, buf + offset, len - offset);

  //-----------------------------------------------------------------------------
  // XXX: Dump result
  //-----------------------------------------------------------------------------
  fprintf(stdout, "Priority: %s\n", priority);
  fprintf(stdout, "Version: %s\n", version);
  fprintf(stdout, "Timestamp: %s\n", timestamp);
  fprintf(stdout, "Hostname: %s\n", hostname);
  fprintf(stdout, "APP-Name: %s\n", appname);
  fprintf(stdout, "PROCID: %s\n", procid);
  fprintf(stdout, "MSGID: %s\n", msgid);
  fprintf(stdout, "Message: %s\n", message);

  //-----------------------------------------------------------------------------
  // XXX: Compose SQL statement
  //-----------------------------------------------------------------------------
}

//------------------------------------------------------------------------------
// XXX: Function test
//------------------------------------------------------------------------------
// *IDENT-OFF*
static char *rfc5424_examples_data[] = 
{
  // with no STRUCTURED-DATA
  "<34>1 2003-10-11T22:14:15.003Z mymachine.example.com su - ID47 - BOM'su root' failed for lonvick on /dev/pts/8",
  // with no STRUCTURED-DATA
  "<165>1 2003-08-24T05:14:15.000003-07:00 192.0.2.1 myproc 8710 - - %% It's time to make the do-nuts.",
  // with STRUCTURED-DATA
  "<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"] BOMAn application event log entry...",
  // STRUCTURED-DATA Only
  "<165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32473 class=\"high\"]",
  NULL
};
// *IDENT-ON*

//------------------------------------------------------------------------------
// XXX: Function test
//------------------------------------------------------------------------------
void rfc5424_function_test(void)
{
  char **p = rfc5424_examples_data;

  while (p && *p)
  {
    fprintf(stdout, "%s\n", *p);
    parse_rfc5424_raw(*p, strlen(*p));
    p++;
  }
}
